Crate bevy_mod_sysfail

source ·
Expand description

Bevy system error handling

Bevy tracking Latest version Apache 2.0 Documentation

Decorate your bevy system with the sysfail macro attribute to handle failure.

Before
use bevy::prelude::*;
use bevy::utils::Duration;

use thiserror::Error;

#[derive(Error, Debug)]
enum GizmoError {
    #[error("A Gizmo error")]
    Error,
}

#[derive(Debug, PartialEq, Eq, Hash, SystemSet, Clone)]
enum TransformGizmoSystem { Drag, Place }

fn main() {
    let mut app = App::new();
    app.add_plugins(bevy::time::TimePlugin)
        .add_systems(Update, (
            drag_gizmo
                .pipe(print_gizmo_error)
                .in_set(TransformGizmoSystem::Drag),
            delete_gizmo
                .pipe(|In(_)| {})
                .after(TransformGizmoSystem::Place),
            place_gizmo
                .pipe(print_gizmo_error)
                .in_set(TransformGizmoSystem::Place)
                .after(TransformGizmoSystem::Drag),
        ));
    app.update();
}

fn print_gizmo_error(
    In(result): In<Result<(), Box<dyn std::error::Error>>>,
    mut last_error_occurence: Local<Option<Duration>>,
    time: Res<Time>,
) {
  // error boilerplate, may include
  // - avoiding printing multiple times the same error
  // - Formatting and chosing the log level
}

fn drag_gizmo(time: Res<Time>) -> Result<(), Box<dyn std::error::Error>> {
    println!("drag time is: {}", time.elapsed_seconds());
    let _ = Err(GizmoError::Error)?;
    println!("This will never print");
    Ok(())
}

fn place_gizmo() -> Result<(), Box<dyn std::error::Error>> {
    let () = Result::<(), &'static str>::Ok(())?;
    println!("this line should actually show up");
    let _ = Err("Ah, some creative use of info logging I see")?;
    Ok(())
}

fn delete_gizmo(time: Res<Time>) -> Option<()> {
    println!("delete time is: {}", time.elapsed_seconds());
    let _ = None?;
    println!("This will never print");
    Some(())
}
After
use bevy::prelude::*;
use bevy_mod_sysfail::macros::*;

use thiserror::Error;

#[derive(Error, Debug)]
enum GizmoError {
    #[error("A Gizmo error")]
    Error,
}

fn main() {
    let mut app = App::new();
    app.add_plugins(bevy::time::TimePlugin)
        .add_systems(Update, (
            drag_gizmo,
            delete_gizmo.after(place_gizmo),
            place_gizmo.after(drag_gizmo)
        ));
    app.update();
}

#[sysfail(log)]
fn drag_gizmo(time: Res<Time>) -> Result<(), anyhow::Error> {
    println!("drag time is: {}", time.elapsed_seconds());
    let _ = Err(GizmoError::Error)?;
    println!("This will never print");
    Ok(())
}

#[sysfail(log(level = "info"))]
fn place_gizmo() -> Result<(), &'static str> {
    let () = Result::<(), &'static str>::Ok(())?;
    println!("this line should actually show up");
    let _ = Err("Ah, some creative use of info logging I see")?;
    Ok(())
}

#[quick_sysfail]
fn delete_gizmo(time: Res<Time>) {
    println!("delete time is: {}", time.elapsed_seconds());
    let _ = None?;
    println!("This will never print");
}

sysfail attribute

sysfail is an attribute macro you can slap on top of your systems to define the handling of errors. Unlike pipe, this is done directly at the definition site, and not when adding to the app. As a result, it’s easy to see at a glance what kind of error handling is happening in the system, it also allows using the system name as a label in system dependency specification.

The sysfail attribute can only be used on systems returning a type implementing the Failure trait. Failure is implemented for Result<(), impl FailureMode> and Option<()>. sysfail takes a single argument, it is one of the following:

  • log: print the Err of the Result return value, prints a very generic “A none value” when the return type is Option. By default, most things are logged at error level.
  • log(level = "{silent,trace,debug,info,warn,error}"): This forces logging of errors at a certain level (make sure to add the quotes)
  • ignore: This is a shortcut for log(level = "silent")

Note that when the level is not "silent", bevy_mod_sysfail adds the Res<Time> and Local<LoggedErrors> system parameters to be able to supress repeating error messages.

quick_sysfail attribute

quick_sysfail is like sysfail(ignore) but only works on Option<()>. This attribute, unlike sysfail allows you to elide the final Some(()) and the type signature of the system. It’s for the maximally lazy, like me.

use bevy_mod_sysfail::macros::*;

#[sysfail(ignore)]
fn place_gizmo() -> Option<()> {
  // …
  Some(())
}
// equivalent to:
#[quick_sysfail]
fn quick_place_gizmo() {
  // …
}

Traits

How error is handled is not very customizable, but there is a few behaviors controllable by the user, always through traits.

Failure trait

Failure is implemented for Result<(), impl FailureMode> and Option<()>.

Systems marked with the sysfail attribute must return a type implementing Failure.

FailureMode trait

FailureMode defines how the failure is handled. By implementing the trait on your own error types, you can specify:

  • What constitutes “distinct” error types.
  • How long an error must not be produced in order to be displayed again.

FailureMode is implemented for Box<dyn Error>, anyhow::Error, () and &'static str.

Change log

See CHANGELOG.md

Version Matrix

bevylatest supporting version
0.125.0.0
0.114.3.0
0.102.0.0
0.91.1.0
0.80.1.0

License

Copyright © 2022 Nicola Papale

This software is licensed under Apache 2.0.

Modules

Structs

  • Tracks when specific errors were logged.
  • OverrideLevelDeprecated
    Deprecated, bevy_mod_sysfail doesn’t respect the runtime-set log level.

Enums

  • LogLevelDeprecated
    Deprecated: It is now completely unused.

Traits

  • Something that can be returned by a function marked with #[sysfail(log)].
  • Something that can be logged in a sysfail handler
  • Deprecated: This is completely ignored by bevy_mod_sysfail.

Attribute Macros

  • quick_sysfail is like sysfail(ignore) but only works on Option<()>.
  • sysfail is an attribute macro you can slap on top of your systems to define the handling of errors.